/*
  ==============================================================================

    AudioSettingsComponent.cpp
    Created: 11 Jun 2011 7:45:58pm
    Author:  David Rowland

  ==============================================================================
*/

#include "AudioSettingsComponent.h"

//==============================================================================
class SimpleDeviceManagerInputLevelMeter  : public Component,
public Timer
{
public:
    SimpleDeviceManagerInputLevelMeter (AudioDeviceManager* const manager_)
	: manager (manager_),
	level (0)
    {
        startTimer (50);
        manager->enableInputLevelMeasurement (true);
    }
	
    ~SimpleDeviceManagerInputLevelMeter()
    {
        manager->enableInputLevelMeasurement (false);
    }
	
    void timerCallback()
    {
        const float newLevel = (float) manager->getCurrentInputLevel();
		
        if (std::abs (level - newLevel) > 0.005f)
        {
            level = newLevel;
            repaint();
        }
    }
	
    void paint (Graphics& g)
    {
        getLookAndFeel().drawLevelMeter (g, getWidth(), getHeight(),
                                         (float) exp (log (level) / 3.0)); // (add a bit of a skew to make the level more obvious)
    }
	
private:
    AudioDeviceManager* const manager;
    float level;
	
    JUCE_DECLARE_NON_COPYABLE (SimpleDeviceManagerInputLevelMeter);
};


//==============================================================================
class AudioSettingsComponent::MidiInputSelectorComponentListBox  : public ListBox,
public ListBoxModel
{
public:
    //==============================================================================
    MidiInputSelectorComponentListBox (AudioDeviceManager& deviceManager_,
                                       const String& noItemsMessage_,
                                       const int minNumber_,
                                       const int maxNumber_)
	: ListBox (String::empty, nullptr),
	deviceManager (deviceManager_),
	noItemsMessage (noItemsMessage_),
	minNumber (minNumber_),
	maxNumber (maxNumber_)
    {
        items = MidiInput::getDevices();
		
        setModel (this);
        setOutlineThickness (1);
    }
	
    ~MidiInputSelectorComponentListBox()
    {
    }
	
    int getNumRows()
    {
        return items.size();
    }
	
    void paintListBoxItem (int row,
                           Graphics& g,
                           int width, int height,
                           bool rowIsSelected)
    {
        if (isPositiveAndBelow (row, items.size()))
        {
            if (rowIsSelected)
                g.fillAll (findColour (TextEditor::highlightColourId)
						   .withMultipliedAlpha (0.3f));
			
            const String item (items [row]);
            bool enabled = deviceManager.isMidiInputEnabled (item);
			
            const int x = getTickX();
            const float tickW = height * 0.75f;
			
            getLookAndFeel().drawTickBox (g, *this, x - tickW, (height - tickW) / 2, tickW, tickW,
                                          enabled, true, true, false);
			
            g.setFont (height * 0.6f);
            g.setColour (findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f));
            g.drawText (item, x, 0, width - x - 2, height, Justification::centredLeft, true);
        }
    }
	
    void listBoxItemClicked (int row, const MouseEvent& e)
    {
        selectRow (row);
		
        if (e.x < getTickX())
            flipEnablement (row);
    }
	
    void listBoxItemDoubleClicked (int row, const MouseEvent&)
    {
        flipEnablement (row);
    }
	
    void returnKeyPressed (int row)
    {
        flipEnablement (row);
    }
	
    void paint (Graphics& g)
    {
        ListBox::paint (g);
		
        if (items.size() == 0)
        {
            g.setColour (Colours::grey);
            g.setFont (13.0f);
            g.drawText (noItemsMessage,
                        0, 0, getWidth(), getHeight() / 2,
                        Justification::centred, true);
        }
    }
	
    int getBestHeight (const int preferredHeight)
    {
        const int extra = getOutlineThickness() * 2;
		
        return jmax (getRowHeight() * 2 + extra,
                     jmin (getRowHeight() * getNumRows() + extra,
                           preferredHeight));
    }
	
private:
    //==============================================================================
    AudioDeviceManager& deviceManager;
    const String noItemsMessage;
    StringArray items;
    int minNumber, maxNumber;
	
    void flipEnablement (const int row)
    {
        if (isPositiveAndBelow (row, items.size()))
        {
            const String item (items [row]);
            deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item));
        }
    }
	
    int getTickX() const
    {
        return getRowHeight() + 5;
    }
	
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInputSelectorComponentListBox);
};

//==============================================================================
struct AudioDeviceSetupDetails
{
    AudioDeviceManager* manager;
    int minNumInputChannels, maxNumInputChannels;
    int minNumOutputChannels, maxNumOutputChannels;
    bool useStereoPairs;
};

//==============================================================================
class AudioDeviceSettingsPanel : public Component,
public ChangeListener,
public ComboBoxListener,  // (can't use ComboBox::Listener due to idiotic VC2005 bug)
public ButtonListener
{
public:
    AudioDeviceSettingsPanel (AudioIODeviceType* type_,
                              AudioDeviceSetupDetails& setup_,
                              const bool hideAdvancedOptionsWithButton)
	:	type (type_),
		setup (setup_)
    {
		addAndMakeVisible(senderLabel = new Label("senderLabel", "When Sending:"));
		addAndMakeVisible(receiverLabel = new Label("receiverLabel", "When Receiving:"));
		
        if (hideAdvancedOptionsWithButton)
        {
            addAndMakeVisible (showAdvancedSettingsButton = new TextButton (TRANS("Show advanced settings...")));
            showAdvancedSettingsButton->addListener (this);
        }
		
        type->scanForDevices();
		
        setup.manager->addChangeListener (this);
        updateAllControls();
    }
	
    ~AudioDeviceSettingsPanel()
    {
        setup.manager->removeChangeListener (this);
    }
	
    void resized()
    {
        const int lx = proportionOfWidth (0.35f);
        const int w = proportionOfWidth (0.4f);
        const int h = 24;
        const int space = 6;
        const int dh = h + space;
        int y = 0;
		
		if (sampleRateDropDown != nullptr)
        {
            sampleRateDropDown->setVisible (showAdvancedSettingsButton == nullptr
											|| ! showAdvancedSettingsButton->isVisible());
			
            sampleRateDropDown->setBounds (lx, y, proportionOfWidth (0.6f), h);
            y += dh;
        }
		
        if (bufferSizeDropDown != nullptr)
        {
            bufferSizeDropDown->setVisible (showAdvancedSettingsButton == nullptr
											|| ! showAdvancedSettingsButton->isVisible());
            bufferSizeDropDown->setBounds (lx, y, proportionOfWidth (0.6f), h);
            y += dh;
        }
		
		y += space * 2;
		
		receiverLabel->setBounds(0, y, w, 20);
		y += 20 + space;
		
		if (outputDeviceDropDown != nullptr)
        {
            outputDeviceDropDown->setBounds (lx, y, w, h);
			
            if (testButton != nullptr)
                testButton->setBounds (proportionOfWidth (0.77f),
                                       outputDeviceDropDown->getY(),
                                       proportionOfWidth (0.18f),
                                       h);
            y += dh;
        }
		
		const int maxBoxHeight = 100;//(getHeight() - y - dh * 2) / numBoxes;
		
        if (outputChanList != nullptr)
        {
            const int bh = outputChanList->getBestHeight (maxBoxHeight);
            outputChanList->setBounds (lx, y, proportionOfWidth (0.60f), bh);
            y += bh + space;
        }
				
		y += space * 2;

		senderLabel->setBounds(0, y, w, 20);
		y += 20 + space;

		if (inputDeviceDropDown != nullptr)
        {
            inputDeviceDropDown->setBounds (lx, y, w, h);
			
            inputLevelMeter->setBounds (proportionOfWidth (0.77f),
                                        inputDeviceDropDown->getY(),
                                        proportionOfWidth (0.18f),
                                        h);
            y += dh;
        }

		if (inputChanList != nullptr)
        {
            const int bh = inputChanList->getBestHeight (maxBoxHeight);
            inputChanList->setBounds (lx, y, proportionOfWidth (0.60f), bh);
            y += bh + space;
        }
								
        y += space * 2;
		
        if (showAdvancedSettingsButton != nullptr)
        {
            showAdvancedSettingsButton->changeWidthToFitText (h);
            showAdvancedSettingsButton->setTopLeftPosition (lx, y);
        }
				
        if (showUIButton != nullptr)
        {
            showUIButton->setVisible (showAdvancedSettingsButton == nullptr
									  || ! showAdvancedSettingsButton->isVisible());
            showUIButton->changeWidthToFitText (h);
            showUIButton->setTopLeftPosition (lx, y);
        }
    }
	
    void comboBoxChanged (ComboBox* comboBoxThatHasChanged)
    {
        if (comboBoxThatHasChanged == nullptr)
            return;
		
        AudioDeviceManager::AudioDeviceSetup config;
        setup.manager->getAudioDeviceSetup (config);
        String error;
		
        if (comboBoxThatHasChanged == outputDeviceDropDown
			|| comboBoxThatHasChanged == inputDeviceDropDown)
        {
            if (outputDeviceDropDown != nullptr)
                config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty
				: outputDeviceDropDown->getText();
			
            if (inputDeviceDropDown != nullptr)
                config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty
				: inputDeviceDropDown->getText();
			
            if (! type->hasSeparateInputsAndOutputs())
                config.inputDeviceName = config.outputDeviceName;
			
            if (comboBoxThatHasChanged == inputDeviceDropDown)
                config.useDefaultInputChannels = true;
            else
                config.useDefaultOutputChannels = true;
			
            error = setup.manager->setAudioDeviceSetup (config, true);
			
            showCorrectDeviceName (inputDeviceDropDown, true);
            showCorrectDeviceName (outputDeviceDropDown, false);
			
            updateControlPanelButton();
            resized();
        }
        else if (comboBoxThatHasChanged == sampleRateDropDown)
        {
            if (sampleRateDropDown->getSelectedId() > 0)
            {
                config.sampleRate = sampleRateDropDown->getSelectedId();
                error = setup.manager->setAudioDeviceSetup (config, true);
            }
        }
        else if (comboBoxThatHasChanged == bufferSizeDropDown)
        {
            if (bufferSizeDropDown->getSelectedId() > 0)
            {
                config.bufferSize = bufferSizeDropDown->getSelectedId();
                error = setup.manager->setAudioDeviceSetup (config, true);
            }
        }
		
        if (error.isNotEmpty())
        {
            AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
                                              "Error when trying to open audio device!",
                                              error);
        }
    }
	
    void buttonClicked (Button* button)
    {
        if (button == showAdvancedSettingsButton)
        {
            showAdvancedSettingsButton->setVisible (false);
            resized();
        }
        else if (button == showUIButton)
        {
            AudioIODevice* const device = setup.manager->getCurrentAudioDevice();
			
            if (device != nullptr && device->showControlPanel())
            {
                setup.manager->closeAudioDevice();
                setup.manager->restartLastAudioDevice();
                getTopLevelComponent()->toFront (true);
            }
        }
        else if (button == testButton && testButton != nullptr)
        {
            setup.manager->playTestSound();
        }
    }
	
    void updateAllControls()
    {
        updateOutputsComboBox();
        updateInputsComboBox();
		
        updateControlPanelButton();
		
        AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice();
		
        if (currentDevice != nullptr)
        {
            if (setup.maxNumOutputChannels > 0
				&& setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size())
            {
                if (outputChanList == nullptr)
                {
                    addAndMakeVisible (outputChanList
									   = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType,
																	 TRANS ("(no audio output channels found)")));
                    outputChanLabel = new Label (String::empty, TRANS ("active output channels:"));
                    outputChanLabel->attachToComponent (outputChanList, true);
                }
				
                outputChanList->refresh();
            }
            else
            {
                outputChanLabel = nullptr;
                outputChanList = nullptr;
            }
			
            if (setup.maxNumInputChannels > 0
				&& setup.minNumInputChannels < setup.manager->getCurrentAudioDevice()->getInputChannelNames().size())
            {
                if (inputChanList == nullptr)
                {
                    addAndMakeVisible (inputChanList
									   = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType,
																	 TRANS ("(no audio input channels found)")));
                    inputChanLabel = new Label (String::empty, TRANS ("active input channels:"));
                    inputChanLabel->attachToComponent (inputChanList, true);
                }
				
                inputChanList->refresh();
            }
            else
            {
                inputChanLabel = nullptr;
                inputChanList = nullptr;
            }
			
            updateSampleRateComboBox (currentDevice);
            updateBufferSizeComboBox (currentDevice);
        }
        else
        {
            jassert (setup.manager->getCurrentAudioDevice() == nullptr); // not the correct device type!
			
            sampleRateLabel = nullptr;
            bufferSizeLabel = nullptr;
            sampleRateDropDown = nullptr;
            bufferSizeDropDown = nullptr;
			
            if (outputDeviceDropDown != nullptr)
                outputDeviceDropDown->setSelectedId (-1, true);
			
            if (inputDeviceDropDown != nullptr)
                inputDeviceDropDown->setSelectedId (-1, true);
        }
		
        resized();
        setSize (getWidth(), getLowestY() + 4);
    }
	
    void changeListenerCallback (ChangeBroadcaster*)
    {
        updateAllControls();
    }
	
private:
    AudioIODeviceType* const type;
    const AudioDeviceSetupDetails setup;
	
    ScopedPointer<ComboBox> outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown;
    ScopedPointer<Label> outputDeviceLabel, inputDeviceLabel, sampleRateLabel, bufferSizeLabel, senderLabel, inputChanLabel, receiverLabel, outputChanLabel;
    ScopedPointer<TextButton> testButton;
    ScopedPointer<Component> inputLevelMeter;
    ScopedPointer<TextButton> showUIButton, showAdvancedSettingsButton;
	
    void showCorrectDeviceName (ComboBox* const box, const bool isInput)
    {
        if (box != nullptr)
        {
            AudioIODevice* const currentDevice = dynamic_cast <AudioIODevice*> (setup.manager->getCurrentAudioDevice());
			
            const int index = type->getIndexOfDevice (currentDevice, isInput);
			
            box->setSelectedId (index + 1, true);
			
            if (testButton != nullptr && ! isInput)
                testButton->setEnabled (index >= 0);
        }
    }
	
    void addNamesToDeviceBox (ComboBox& combo, bool isInputs)
    {
        const StringArray devs (type->getDeviceNames (isInputs));
		
        combo.clear (true);
		
        for (int i = 0; i < devs.size(); ++i)
            combo.addItem (devs[i], i + 1);
		
        combo.addItem (TRANS("<< none >>"), -1);
        combo.setSelectedId (-1, true);
    }
	
    int getLowestY() const
    {
        int y = 0;
		
        for (int i = getNumChildComponents(); --i >= 0;)
            y = jmax (y, getChildComponent (i)->getBottom());
		
        return y;
    }
	
	void updateControlPanelButton()
    {
        AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice();
        showUIButton = nullptr;
		
        if (currentDevice != nullptr && currentDevice->hasControlPanel())
        {
            addAndMakeVisible (showUIButton = new TextButton (TRANS ("show this device's control panel"),
                                                              TRANS ("opens the device's own control panel")));
            showUIButton->addListener (this);
        }
		
        resized();
    }
	
    void updateOutputsComboBox()
    {
        if (setup.maxNumOutputChannels > 0 || ! type->hasSeparateInputsAndOutputs())
        {
            if (outputDeviceDropDown == nullptr)
            {
                outputDeviceDropDown = new ComboBox (String::empty);
                outputDeviceDropDown->addListener (this);
                addAndMakeVisible (outputDeviceDropDown);
				
                outputDeviceLabel = new Label (String::empty,
                                               type->hasSeparateInputsAndOutputs() ? TRANS ("output:")
											   : TRANS ("device:"));
                outputDeviceLabel->attachToComponent (outputDeviceDropDown, true);
				
                if (setup.maxNumOutputChannels > 0)
                {
                    addAndMakeVisible (testButton = new TextButton (TRANS ("Test")));
                    testButton->addListener (this);
                }
            }
			
            addNamesToDeviceBox (*outputDeviceDropDown, false);
        }
		
        showCorrectDeviceName (outputDeviceDropDown, false);
    }
	
    void updateInputsComboBox()
    {
        if (setup.maxNumInputChannels > 0 && type->hasSeparateInputsAndOutputs())
        {
            if (inputDeviceDropDown == nullptr)
            {
                inputDeviceDropDown = new ComboBox (String::empty);
                inputDeviceDropDown->addListener (this);
                addAndMakeVisible (inputDeviceDropDown);
				
                inputDeviceLabel = new Label (String::empty, TRANS ("input:"));
                inputDeviceLabel->attachToComponent (inputDeviceDropDown, true);
				
                addAndMakeVisible (inputLevelMeter
								   = new SimpleDeviceManagerInputLevelMeter (setup.manager));
            }
			
            addNamesToDeviceBox (*inputDeviceDropDown, true);
        }
		
        showCorrectDeviceName (inputDeviceDropDown, true);
    }
	
    void updateSampleRateComboBox (AudioIODevice* currentDevice)
    {
        if (sampleRateDropDown == nullptr)
        {
            addAndMakeVisible (sampleRateDropDown = new ComboBox (String::empty));
			
            sampleRateLabel = new Label (String::empty, TRANS ("sample rate:"));
            sampleRateLabel->attachToComponent (sampleRateDropDown, true);
        }
        else
        {
            sampleRateDropDown->clear();
            sampleRateDropDown->removeListener (this);
        }
		
        const int numRates = currentDevice->getNumSampleRates();
		
        for (int i = 0; i < numRates; ++i)
        {
            const int rate = roundToInt (currentDevice->getSampleRate (i));
            sampleRateDropDown->addItem (String (rate) + " Hz", rate);
        }
		
        sampleRateDropDown->setSelectedId (roundToInt (currentDevice->getCurrentSampleRate()), true);
        sampleRateDropDown->addListener (this);
    }
	
    void updateBufferSizeComboBox (AudioIODevice* currentDevice)
    {
        if (bufferSizeDropDown == nullptr)
        {
            addAndMakeVisible (bufferSizeDropDown = new ComboBox (String::empty));
			
            bufferSizeLabel = new Label (String::empty, TRANS ("audio buffer size:"));
            bufferSizeLabel->attachToComponent (bufferSizeDropDown, true);
        }
        else
        {
            bufferSizeDropDown->clear();
            bufferSizeDropDown->removeListener (this);
        }
		
        const int numBufferSizes = currentDevice->getNumBufferSizesAvailable();
        double currentRate = currentDevice->getCurrentSampleRate();
        if (currentRate == 0)
            currentRate = 48000.0;
		
        for (int i = 0; i < numBufferSizes; ++i)
        {
            const int bs = currentDevice->getBufferSizeSamples (i);
            bufferSizeDropDown->addItem (String (bs)
										 + " samples ("
										 + String (bs * 1000.0 / currentRate, 1)
										 + " ms)",
                                         bs);
        }
		
        bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), true);
        bufferSizeDropDown->addListener (this);
    }
	
public:
    //==============================================================================
    class ChannelSelectorListBox  : public ListBox,
	public ListBoxModel
    {
    public:
        enum BoxType
        {
            audioInputType,
            audioOutputType
        };
		
        //==============================================================================
        ChannelSelectorListBox (const AudioDeviceSetupDetails& setup_,
                                const BoxType type_,
                                const String& noItemsMessage_)
		: ListBox (String::empty, nullptr),
		setup (setup_),
		type (type_),
		noItemsMessage (noItemsMessage_)
        {
            refresh();
            setModel (this);
            setOutlineThickness (1);
        }
		
        ~ChannelSelectorListBox()
        {
        }
		
        void refresh()
        {
            items.clear();
			
            AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice();
			
            if (currentDevice != nullptr)
            {
                if (type == audioInputType)
                    items = currentDevice->getInputChannelNames();
                else if (type == audioOutputType)
                    items = currentDevice->getOutputChannelNames();
				
                if (setup.useStereoPairs)
                {
                    StringArray pairs;
					
                    for (int i = 0; i < items.size(); i += 2)
                    {
                        const String name (items[i]);
                        const String name2 (items[i + 1]);
                        String commonBit;
						
                        for (int j = 0; j < name.length(); ++j)
                            if (name.substring (0, j).equalsIgnoreCase (name2.substring (0, j)))
                                commonBit = name.substring (0, j);
						
                        // Make sure we only split the name at a space, because otherwise, things
                        // like "input 11" + "input 12" would become "input 11 + 2"
                        while (commonBit.isNotEmpty() && ! CharacterFunctions::isWhitespace (commonBit.getLastCharacter()))
                            commonBit = commonBit.dropLastCharacters (1);
						
                        pairs.add (name.trim() + " + " + name2.substring (commonBit.length()).trim());
                    }
					
                    items = pairs;
                }
            }
			
            updateContent();
            repaint();
        }
		
        int getNumRows()
        {
            return items.size();
        }
		
        void paintListBoxItem (int row,
                               Graphics& g,
                               int width, int height,
                               bool rowIsSelected)
        {
            if (isPositiveAndBelow (row, items.size()))
            {
                if (rowIsSelected)
                    g.fillAll (findColour (TextEditor::highlightColourId)
							   .withMultipliedAlpha (0.3f));
				
                const String item (items [row]);
                bool enabled = false;
				
                AudioDeviceManager::AudioDeviceSetup config;
                setup.manager->getAudioDeviceSetup (config);
				
                if (setup.useStereoPairs)
                {
                    if (type == audioInputType)
                        enabled = config.inputChannels [row * 2] || config.inputChannels [row * 2 + 1];
                    else if (type == audioOutputType)
                        enabled = config.outputChannels [row * 2] || config.outputChannels [row * 2 + 1];
                }
                else
                {
                    if (type == audioInputType)
                        enabled = config.inputChannels [row];
                    else if (type == audioOutputType)
                        enabled = config.outputChannels [row];
                }
				
                const int x = getTickX();
                const float tickW = height * 0.75f;
				
                getLookAndFeel().drawTickBox (g, *this, x - tickW, (height - tickW) / 2, tickW, tickW,
                                              enabled, true, true, false);
				
                g.setFont (height * 0.6f);
                g.setColour (findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f));
                g.drawText (item, x, 0, width - x - 2, height, Justification::centredLeft, true);
            }
        }
		
        void listBoxItemClicked (int row, const MouseEvent& e)
        {
            selectRow (row);
			
            if (e.x < getTickX())
                flipEnablement (row);
        }
		
        void listBoxItemDoubleClicked (int row, const MouseEvent&)
        {
            flipEnablement (row);
        }
		
        void returnKeyPressed (int row)
        {
            flipEnablement (row);
        }
		
        void paint (Graphics& g)
        {
            ListBox::paint (g);
			
            if (items.size() == 0)
            {
                g.setColour (Colours::grey);
                g.setFont (13.0f);
                g.drawText (noItemsMessage,
                            0, 0, getWidth(), getHeight() / 2,
                            Justification::centred, true);
            }
        }
		
        int getBestHeight (int maxHeight)
        {
            return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()),
                                            getNumRows())
			+ getOutlineThickness() * 2;
        }
		
    private:
        //==============================================================================
        const AudioDeviceSetupDetails setup;
        const BoxType type;
        const String noItemsMessage;
        StringArray items;
		
        void flipEnablement (const int row)
        {
            jassert (type == audioInputType || type == audioOutputType);
			
            if (isPositiveAndBelow (row, items.size()))
            {
                AudioDeviceManager::AudioDeviceSetup config;
                setup.manager->getAudioDeviceSetup (config);
				
                if (setup.useStereoPairs)
                {
                    BigInteger bits;
                    BigInteger& original = (type == audioInputType ? config.inputChannels
											: config.outputChannels);
					
                    int i;
                    for (i = 0; i < 256; i += 2)
                        bits.setBit (i / 2, original [i] || original [i + 1]);
					
                    if (type == audioInputType)
                    {
                        config.useDefaultInputChannels = false;
                        flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2);
                    }
                    else
                    {
                        config.useDefaultOutputChannels = false;
                        flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2);
                    }
					
                    for (i = 0; i < 256; ++i)
                        original.setBit (i, bits [i / 2]);
                }
                else
                {
                    if (type == audioInputType)
                    {
                        config.useDefaultInputChannels = false;
                        flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels);
                    }
                    else
                    {
                        config.useDefaultOutputChannels = false;
                        flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels);
                    }
                }
				
                String error (setup.manager->setAudioDeviceSetup (config, true));
				
                if (! error.isEmpty())
                {
                    //xxx
                }
            }
        }
		
        static void flipBit (BigInteger& chans, int index, int minNumber, int maxNumber)
        {
            const int numActive = chans.countNumberOfSetBits();
			
            if (chans [index])
            {
                if (numActive > minNumber)
                    chans.setBit (index, false);
            }
            else
            {
                if (numActive >= maxNumber)
                {
                    const int firstActiveChan = chans.findNextSetBit();
					
                    chans.setBit (index > firstActiveChan
								  ? firstActiveChan : chans.getHighestBit(),
                                  false);
                }
				
                chans.setBit (index, true);
            }
        }
		
        int getTickX() const
        {
            return getRowHeight() + 5;
        }
		
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelSelectorListBox);
    };
	
private:
    ScopedPointer<ChannelSelectorListBox> inputChanList, outputChanList;
	
    JUCE_DECLARE_NON_COPYABLE (AudioDeviceSettingsPanel);
};


//==============================================================================
AudioSettingsComponent::AudioSettingsComponent (AudioDeviceManager& deviceManager_,
                                                            const int minInputChannels_,
                                                            const int maxInputChannels_,
                                                            const int minOutputChannels_,
                                                            const int maxOutputChannels_,
                                                            const bool showMidiInputOptions,
                                                            const bool showMidiOutputSelector,
                                                            const bool showChannelsAsStereoPairs_,
                                                            const bool hideAdvancedOptionsWithButton_)
: deviceManager (deviceManager_),
minOutputChannels (minOutputChannels_),
maxOutputChannels (maxOutputChannels_),
minInputChannels (minInputChannels_),
maxInputChannels (maxInputChannels_),
showChannelsAsStereoPairs (showChannelsAsStereoPairs_),
hideAdvancedOptionsWithButton (hideAdvancedOptionsWithButton_),
heightOfComponents(0)
{
    jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels);
    jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels);
	
    const OwnedArray<AudioIODeviceType>& types = deviceManager_.getAvailableDeviceTypes();
	
    if (types.size() > 1)
    {
        deviceTypeDropDown = new ComboBox (String::empty);
		
        for (int i = 0; i < types.size(); ++i)
            deviceTypeDropDown->addItem (types.getUnchecked(i)->getTypeName(), i + 1);
		
        addAndMakeVisible (deviceTypeDropDown);
        deviceTypeDropDown->addListener (this);
		
        deviceTypeDropDownLabel = new Label (String::empty, TRANS ("audio device type:"));
        deviceTypeDropDownLabel->setJustificationType (Justification::centredRight);
        deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown, true);
    }
	
    if (showMidiInputOptions)
    {
        addAndMakeVisible (midiInputsList
						   = new MidiInputSelectorComponentListBox (deviceManager,
																	TRANS("(no midi inputs available)"),
																	0, 0));
		
        midiInputsLabel = new Label (String::empty, TRANS ("active midi inputs:"));
        midiInputsLabel->setJustificationType (Justification::topRight);
        midiInputsLabel->attachToComponent (midiInputsList, true);
    }
    else
    {
        midiInputsList = nullptr;
        midiInputsLabel = nullptr;
    }
	
    if (showMidiOutputSelector)
    {
        addAndMakeVisible (midiOutputSelector = new ComboBox (String::empty));
        midiOutputSelector->addListener (this);
		
        midiOutputLabel = new Label ("lm", TRANS("Midi Output:"));
        midiOutputLabel->attachToComponent (midiOutputSelector, true);
    }
    else
    {
        midiOutputSelector = nullptr;
        midiOutputLabel = nullptr;
    }
	
    deviceManager_.addChangeListener (this);
    updateAllControls();
}

AudioSettingsComponent::~AudioSettingsComponent()
{
    deviceManager.removeChangeListener (this);
}

void AudioSettingsComponent::resized()
{
    const int lx = proportionOfWidth (0.35f);
    const int w = proportionOfWidth (0.4f);
    const int h = 24;
    const int space = 6;
    const int dh = h + space;
    int y = 15;
		
	if (deviceTypeDropDown != nullptr)
    {
        deviceTypeDropDown->setBounds (lx, y, proportionOfWidth (0.3f), h);
        y += dh + space * 2;
    }

	if (audioDeviceSettingsComp != nullptr)
    {
        audioDeviceSettingsComp->setBounds (0, y, getWidth(), audioDeviceSettingsComp->getHeight());
        y += audioDeviceSettingsComp->getHeight() + space;
    }
		
    if (midiInputsList != nullptr)
    {
        const int bh = midiInputsList->getBestHeight (jmin (h * 8, getHeight() - y - space - h));
        midiInputsList->setBounds (lx, y, w, bh);
        y += bh + space;
    }
	
    if (midiOutputSelector != nullptr)
	{
        midiOutputSelector->setBounds (lx, y, w, h);
		y += h + space;
	}
	
	//heightOfComponents = y;
	setSize(getWidth(), y);
}

void AudioSettingsComponent::childBoundsChanged (Component* child)
{
    if (child == audioDeviceSettingsComp)
        resized();
}

void AudioSettingsComponent::buttonClicked (Button*)
{
    AudioIODevice* const device = deviceManager.getCurrentAudioDevice();
	
    if (device != nullptr && device->hasControlPanel())
    {
        if (device->showControlPanel())
            deviceManager.restartLastAudioDevice();
		
        getTopLevelComponent()->toFront (true);
    }
}

void AudioSettingsComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
{
    if (comboBoxThatHasChanged == deviceTypeDropDown)
    {
        AudioIODeviceType* const type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1];
		
        if (type != nullptr)
        {
            audioDeviceSettingsComp = nullptr;
			
            deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true);
			
            updateAllControls(); // needed in case the type hasn't actally changed
        }
    }
    else if (comboBoxThatHasChanged == midiOutputSelector)
    {
        deviceManager.setDefaultMidiOutput (midiOutputSelector->getText());
    }
}

void AudioSettingsComponent::changeListenerCallback (ChangeBroadcaster*)
{
    updateAllControls();
}

void AudioSettingsComponent::updateAllControls()
{
    if (deviceTypeDropDown != nullptr)
        deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), false);
	
    if (audioDeviceSettingsComp == nullptr
		|| audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType())
    {
        audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType();
        audioDeviceSettingsComp = nullptr;
		
        AudioIODeviceType* const type
		= deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == nullptr
												   ? 0 : deviceTypeDropDown->getSelectedId() - 1];
		
        if (type != nullptr)
        {
            AudioDeviceSetupDetails details;
            details.manager = &deviceManager;
            details.minNumInputChannels = minInputChannels;
            details.maxNumInputChannels = maxInputChannels;
            details.minNumOutputChannels = minOutputChannels;
            details.maxNumOutputChannels = maxOutputChannels;
            details.useStereoPairs = showChannelsAsStereoPairs;
			
            audioDeviceSettingsComp = new AudioDeviceSettingsPanel (type, details, hideAdvancedOptionsWithButton);
			
            if (audioDeviceSettingsComp != nullptr)
            {
                addAndMakeVisible (audioDeviceSettingsComp);
                audioDeviceSettingsComp->resized();
            }
        }
    }
	
    if (midiInputsList != nullptr)
    {
        midiInputsList->updateContent();
        midiInputsList->repaint();
    }
	
    if (midiOutputSelector != nullptr)
    {
        midiOutputSelector->clear();
		
        const StringArray midiOuts (MidiOutput::getDevices());
		
        midiOutputSelector->addItem (TRANS("<< none >>"), -1);
        midiOutputSelector->addSeparator();
		
        for (int i = 0; i < midiOuts.size(); ++i)
            midiOutputSelector->addItem (midiOuts[i], i + 1);
		
        int current = -1;
		
        if (deviceManager.getDefaultMidiOutput() != nullptr)
            current = 1 + midiOuts.indexOf (deviceManager.getDefaultMidiOutputName());
		
        midiOutputSelector->setSelectedId (current, true);
    }
	
    resized();
}